Dealing with data

The tidyverse is an eco-system of packages that provides a consistent, intuitive system for data manipulation and visualisation in R.

A spreadsheet that we would normally analyse in Excel can can be imported into R by using a function from the readr package.

  • read_csv
  • read_tsv

The readxl package can also import files in Excel’s own format

  • read_xls
  • read_xlsx

We will use the file tcga_clinical_CLEANED.tsv (11MB) as an example. This file contains information about biological samples that were included as part of the TCGA (The Cancer Genome Atlas) project - all tumour types.

If you do not have this file, it can be downloaded with the following code. N.B. the code will first check if the file is present or not.

if(!file.exists("tcga_clinical_CLEANED.tsv")) download.file(url = "https://sbc.shef.ac.uk/r4biol/tcga_clinical_CLEANED.tsv", 
              destfile = "tcga_clinical_CLEANED.tsv")

As the file name ends in .tsv the function we want is probably read_tsv

library(readr)
clinical <- read_tsv("tcga_clinical_CLEANED.tsv")

If you got an error could not find function "read_tsv" at the previous step, it will because you don’t have the readr package installed. In which you will need to run the command:-

install.packages("readr")
library(readr)

This will install the readr package. You will then need to run the read_tsv line of code from above.

If you get really stuck reading data into R, you can use the Import Dataset option from the File menu which will allow you to choose the parameters to read the data interactively

The Environment panel of RStudio (top-right) should show that an object called clinical has been created. This means that we can start doing some analysis on this dataset. The choice to call the object clinical was ours. We could have used any name instead of clinical but we chose something vaguely informative and memorable.

The dimensions of the object should should 7706 obs. of 420 variables. This means the object we have created contains 7706 rows and 420 columns. Each row is a different observation; in this case a different biological sample. Each column records the value of a different variable. In R’s terminology, we have just created a tibble which is a special case of something called a data.frame. As we will see the the object can contain either numbers or text in each column.

RStudio has a browsing functionality available that we can activate using the View function

View(clinical)

Observations that are missing are indicated by an NA. It may look at first glance that the table mostly contains missing data. This is because some columns are only recorded for particular tumour types and not all tumour types recorded the same information.

Data Exploration

We will now start to explore the data with a couple of different questions in mind:-

  • What are the differences in male/female split between different tumour types?
  • Is there a difference in diagnosis age in different tumour types?

The tasks required to do this will be greatly helped by the dplyr package.

library(dplyr)

As with the readr package above, you will need to have installed the dplyr package beforehand.

install.packages("dplyr")

Choosing what columns to analyse

The select function allows us to narrow down the number of variables we are interested in from 420. The first argument is always the name of the data frame. There are numerous different ways of specifying which column(s) you want, including typing the names of the columns of interest in exactly the same way that they appear in the data. Let’s assume we already know the names of columns containing tumour type and gender.

The dataset utilizes a binary classification of gender as ‘male’ or ‘female’. It is important to note that this categorization may not fully encompass the diverse range of gender identities recognized today.

##Note that the spelling of "tumor" has to exactly match that found in the data
select(clinical, 
       tumor_tissue_site,
       gender
       )

We can reasonably guess that the Age information would be contained in a column that has “age” somewhere in the name. However, the column is not just called “age” or “Age”

select(clinical, age)
select(clinical, Age)

Without manually going through the columns, there are a few “helper” functions that we can employ

select(clinical, contains("age"))
select(clinical, contains("age_"))
select(clinical, starts_with("age"))

Up to now we have not changed the underlying dataset. select is showing what the dataset looks like after applying the specified subset. If we want to make permanent changes we can create a variable

analysis_data <- select(clinical,
                        bcr_patient_barcode,
                        tumor_tissue_site,
                        gender,
                        age_at_diagnosis)

Restricting rows

The select function only performs the very specific task of letting you choose what columns you want to analyse. After using select, our dataset analysis_data still has all 2496 rows.

The function to choose or restrict to the rows we might be interested in is called filter. We have to write a short R command to choose the rows.

e.g. if we want only the male samples we use the following code. Notice that two “=” signs are required. If you try and use the function with a single “=” R will print a helpful hint.

filter(analysis_data, gender == "MALE")

and the females

filter(analysis_data, gender == "FEMALE")

An equivalent, but somewhat unnecessary for this example, statement would be to ask for rows where the gender is not equal to MALE

filter(analysis_data, gender != "MALE")

Identifying male or females based on the gender column is simplified by there only being two possible entries in this column; MALE or FEMALE. In a real life situation, especially if data have been entered by hand, there could be inconsistencies that require action. See an example at the end of the tutorial.

We can also restrict the data based on numeric columns by using >, < etc.

filter(analysis_data, age_at_diagnosis > 80)

But what if we want males with Brain tumours? dplyr allows us to combine more than one condition if we separate them with a ,. In computing this is known as an “and” statement and only rows where both statements are two will be shown. The line could be extended to include more than two tests if we wanted to.

filter(analysis_data, gender == "MALE",tumor_tissue_site == "Brain")

How about brain or lung tumours? Using a | symbol instead of a , allows for either of two (or more) conditions to be TRUE.

filter(analysis_data, tumor_tissue_site == "Brain" | tumor_tissue_site == "Lung")

To answer the question of how many males / females have a certain tumour type we could now use R statements such as:-

filter(analysis_data, gender == "MALE",tumor_tissue_site == "Brain")
filter(analysis_data, gender == "FEMALE",tumor_tissue_site == "Brain")

and make a note of the number of observations included in the resulting data frame. However, there is much more flexible way of summarising data in this manner.

Summarising

Although useful for data exploration, it would clearly be inefficient to get gender/tumour type counts in this way as we would have to repeat for all combinations of tumour type and gender. The function count can now give us exactly what we want. The output is given as a tibble, so we could use some of the functions that we have learnt about so far (select, filter…) to further manipulate. e.g. obtain the counts for just Brain/

count(analysis_data, 
      tumor_tissue_site,
      gender)

The count function is useful for tabulating the number of observations, but for other summary statistics a more general sumamrise function can be used. This can be used in conjunction with basic summary functions supported by base R. A summary statistic being something that can be applied to a series of numbers and produce a single number as a result. e.g. the average, minimum, maximum etc.

summarise(analysis_data, 
          Average = mean(age_at_diagnosis),
          min = min(age_at_diagnosis),
          max = max(age_at_diagnosis))

However, we have a problem due to missing values. If R sees and missing values in a column it will report the mean, minimum or maximum of that column as a missing value. Although this default behaviour can be changed, before proceeding we could also choose to remove any missing observations from the data. These are represented by a NA value, which is a special value and not a character label.

filter(analysis_data, is.na(age_at_diagnosis) | is.na(tumor_tissue_site))

We want the opposite of the above; where the age of diagnosis and tumour site is not missing.

analysis_data <- filter(analysis_data, !is.na(age_at_diagnosis), !is.na(tumor_tissue_site))

The summary will now work as expected.

summarise(analysis_data, 
          Average = mean(age_at_diagnosis),
          min = min(age_at_diagnosis),
          max = max(age_at_diagnosis))

This might not be what we want in all circumstances, as the statistics can also be calculated on a per-tumour site basis using dplyrs group_by function.


data_grouped <- group_by(analysis_data, tumor_tissue_site)
summarise(data_grouped,Average = mean(age_at_diagnosis),
          min = min(age_at_diagnosis),
          max = max(age_at_diagnosis) )

Sorting (arranging)

We have previously used filter to restrict the rows that we are interested in. Rather than just analysing the male or female patients (for example), we might also want the rows in our table to be ordered according to the gender column.

arrange(analysis_data, gender)

We can also arrange by columns containing numeric values in either ascending (the default) or descending order.

arrange(analysis_data, age_at_diagnosis)
## Use a descending order
arrange(analysis_data, desc(age_at_diagnosis))

Like how sorting works in Excel, we can also use mutliple columns for sorting. e.g. if we want ordering by diagnosis age for each tumour type separately.

arrange(analysis_data, tumor_tissue_site, age_at_diagnosis)

Workflows and “piping”

So far we have used several operations in isolation. However, the real joy (?) of dplyr is how different operations can be chained together. Lets say we just wanted female tumours.

filter(analysis_data, gender == "FEMALE")

Our next step could be to remove the gender column since it is somewhat redundant.

analysis_data2 <- filter(analysis_data, gender == "FEMALE")
select(analysis_data2, tumor_tissue_site, age_at_diagnosis)
## or 
## select(analysis_data2, -gender)

The code would quickly get cumbersome if we wanted to include additional steps such as removing NA values. An alternative approach called “piping” is recommended and activated by adding %>% at the end of a line. This tells R to use the output of the current line as the first argument on the next line. In this current example it means we don’t need to specify which data frame that select uses as input - it will use the data frame created by the filter in the previous line. The code written using %>% is more concise.

filter(analysis_data, gender == "FEMALE") %>% ## and then...
  select(tumor_tissue_site, age_at_diagnosis) ## %>% and then...

The %>% operation becomes available when you load dplyr. If you wish to use piping outside of dplyr there is also a “base” equivalent |> that doesn’t require any libraries to be loaded

filter(analysis_data, gender == "FEMALE") |> ## and then...
  select(tumor_tissue_site, age_at_diagnosis) ## |> and then...

We recently created a summary table for each tumour type giving the average, minimum and maximum of diagnosis age. This can be replicated using %>% and an extra sorting step added to the end.

group_by(analysis_data, tumor_tissue_site) %>% 
summarise(Average = mean(age_at_diagnosis),
          min = min(age_at_diagnosis),
          max = max(age_at_diagnosis)) %>% 
  arrange(Average)

Overview of plotting

Our recommending way of creating plots in RStudio is to use the ggplot2 package - especially as it interacts well with dplyr and other tidyverse packages.

library(ggplot2)

A couple of useful references are given here-

The general principle of creating a plot is the same regardless of what kind of plot we want to make

  • specify the data frame containing the data we want to plot
  • specify which columns in that data frame we want to use for various aesthetic aspects of the plot
  • define the type of plot we want
  • apply any additional format changes

A bar plot would be a natural choice for showing the counts of male / female samples. The geom_bar plot will automatically count how many occurrences there are for each value.

ggplot(analysis_data, aes(x = gender)) + geom_bar()

Numerical data can be visualised using a density plot or histogram. The density is automatically calculated and displayed on the y-axis.

ggplot(analysis_data, aes(x = age_at_diagnosis)) + geom_density()

In order to compare the age distributions of different tumour types we can also imagine this being displayed as a series of boxplots with

  • the age variable on the y-axis
  • the type of tumour on the x-axis

this can be translated into ggplot2 language as follows -

ggplot(analysis_data, aes(x = tumor_tissue_site, y = age_at_diagnosis)) + geom_boxplot()

A disadvantage of the boxplot is that it only gives a very crude summary of the data. It can be misleading when applied to data with few observations and is often preferable to add individual data points

ggplot(analysis_data, aes(x = tumor_tissue_site, y = age_at_diagnosis)) + geom_boxplot() + geom_jitter(width=0.1)

Adding some colour to the plot can be achieved by adding a fill aesthetic and specifying what column to map the colours too. A colour palette is automatically chosen, but can be changed afterwards if we wish.

ggplot(analysis_data, aes(x = tumor_tissue_site, y = age_at_diagnosis, fill = tumor_tissue_site)) + geom_boxplot() + geom_jitter(width=0.1)

Adding the fill aesthetic for the density plot can be used to show a separate curve for each tumour type.

## alpha of 0.5 used to make the curves transparent
ggplot(analysis_data, aes(x = age_at_diagnosis, fill = tumor_tissue_site)) + geom_density(alpha=0.5)

Another useful technique for splitting the plots based on a variable is to use the facet_wrap function that will give a grid of plots. For instance we can show male/female counts for each tumour type separately.

ggplot(analysis_data, aes(x = gender,fill=gender)) + geom_bar() + facet_wrap(~tumor_tissue_site)

By combining all the techniques we have seen we can compare the diagnosis age between males and females; separately for each tumour type.

ggplot(analysis_data, aes(x =gender, y = age_at_diagnosis, fill = gender)) + geom_boxplot() + geom_jitter(width=0.1) + facet_wrap(~tumor_tissue_site)

Challenges of “messy” data

Real-life data are often less straightforward to deal with than the “cleaned” dataset presented here. Despite the many high-throughput technologies that are used for scientific investigation, there is inevitably a spreadsheet(s) needed to describe the experimental setup and this is typically entered manually.

So-called “Data Wrangling” is a crucial and time-consuming part of the analysis process taking 80% of analysis time by some estimates. Hadley Wickham, Chief Scientist at Posit and lead author of ggplot2 likens tidy and messy data to Leo Tolstoy’s quote about families:-

Happy families are all alike; every unhappy family is unhappy in its own way

Like families, tidy datasets are all alike but every messy dataset is messy in its own way.

A comprehensive guide to the issues surrounding data entry via spreadsheets, and how to avoid them, is given by Data Carpentry.

However, for public data that we have no control over we often have no choice but to clean the data ourselves. We have intentionally created an alternative dataset with a few intentional issues to illustrate the cleaning process. The following code will download the data if you do not have it already.

if(!file.exists("tcga_clinical_MESSY.tsv")) download.file(url = "https://sbc.shef.ac.uk/r4biol/tcga_clinical_MESSY.tsv", 
              destfile = "tcga_clinical_MESSY.tsv")
messy <- read_tsv("tcga_clinical_MESSY.tsv")
messy

Whitespace

“whitespace” is the addition of a blank character or space to the beginning or end of text. Traditionally it is a problem because it will create extra categories in your data. e.g. MALE and MALE. The messy dataset that you have just imported includes some whitespace in the tumor_tissue_site column. However, the read_tsv function automatically ignores whitespace values as the trim_ws argument of read_tsv is set to TRUE (see the help page ?read_tsv).

messy_ws <- read_tsv("tcga_clinical_MESSY.tsv", 
                     trim_ws = FALSE)
messy_ws
count(messy_ws,tumor_tissue_site)

The resulting data frame now contains two apparently identical categories for Bladder. However, with the use of the nchar function, which counts the number of characters, we can see that extra spaces must be included.

count(messy_ws,tumor_tissue_site) %>% 
  mutate("Length_of_Label" = nchar(tumor_tissue_site))

Clearly we could have used the default settings for read_tsv and the problem would not have occurred. Otherwise, it is useful to know about the stringr package that contains many useful functions for cleaning character data.

For the example of removing whitespace we can use the str_trim function combined with a mutate. This will replace all the whitespace in the tumor_tissue_site column and overwrite the column. If we repeat a count afterwards we see only the unique entries that we expect.

library(stringr)
mutate(messy_ws, tumor_tissue_site = str_trim(tumor_tissue_site)) %>%
  count(tumor_tissue_site)

Inconsistent coding of variables

Unfortunately the tumor_tissue_site column is not the only one with issue that need fixing with these data. If, as before, we try and plot the number of males/females in the dataset we get a surprise.

ggplot(messy, aes(x = gender)) + geom_bar()

There is no differentiation between female and FEMALE or male and MALE. Whilst we can intuitively decide that these represent the same value, they do not get automatically combined in R. The consequence being that attempts to identify all the male patients will require some careful coding. The example used previously will now no longer identify all the correct patients.

filter(messy, gender == "MALE")

One solution when filtering would be to add different criteria to account for the different capitalisation

filter(messy, gender == "MALE" | gender == "male")

However, since we know that the error is due to inconsistent use of upper/lowercase we can use the str_to_upper function in stringr to convert all values to uppercase. Or indeed we could convert all to lowercase using str_to_lower if we prefered.

messy %>% 
  mutate(gender = str_to_upper(gender)) %>% 
  ggplot(aes(x = gender)) + geom_bar()

We would probably want to also make the change permanent by creating a new variable

cleaned <- read_tsv("tcga_clinical_MESSY.tsv") %>% 
    mutate(gender = str_to_upper(gender)) 

A more generic approach would be to use the forcats package to replace all occurrences of male with MALE and the same for females.

The package allows us to “recode” entries in a column that contains a factor. i.e. categorical.

library(forcats)
mutate(messy, gender = forcats::fct_recode(gender,"MALE"="male"),
       gender = forcats::fct_recode(gender,"FEMALE"="female")) %>% 
    ggplot(aes(x = gender)) + geom_bar()

The approach is more flexible than merely changing the case, as it could also replace other values such as “m” or “f” if they existed.

## Just example code if we wanted to replace "m" with "MALE"
messy <- messy %>%
  mutate(gender = fct_recode(gender,
                            "MALE" = "male", 
                            "MALE" = "m" 
  ))

Different means of representing missing values

There are many different strategies for representing missing data. The read_tsv function should automatically detect any NA values in the source dataset and treat them appropriately. However, in our messy dataset we also have NULL values (as seen by making a count of the values in age_at_diagnosis).

count(messy, age_at_diagnosis) %>% arrange(desc(n))

Because the NULL value is present in the age_at_diagnosis column, R will treat. the entire column as containing characters. Therefore we cannot use the kind of plots we would expect with numeric data

ggplot(messy, aes(x = age_at_diagnosis)) + geom_histogram()

Likewise we can’t calculate numeric summaries; although R will attempt to and create a data frame of NA values rather than giving an error.

  group_by(messy, tumor_tissue_site) %>% 
  summarise(Mean_Diagnosis_Age = mean(age_at_diagnosis,na.rm=TRUE))
Warning: There were 19 warnings in `summarise()`.
The first warning was:
ℹ In argument: `Mean_Diagnosis_Age = mean(age_at_diagnosis, na.rm = TRUE)`.
ℹ In group 1: `tumor_tissue_site = "Bladder"`.
Caused by warning in `mean.default()`:
! argument is not numeric or logical: returning NA
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 18 remaining warnings.

The read_tsv function has the ability to replace NA values when the data are imported. Specifically, the na argument can be used to define what values are being used to represent missing.

read_tsv("tcga_clinical_MESSY.tsv", na = c("NULL","NA")) %>% 
  ggplot(aes(x = age_at_diagnosis)) + geom_histogram()

read_tsv("tcga_clinical_MESSY.tsv", na = c("NULL","NA")) %>% 
  group_by(tumor_tissue_site) %>% 
  summarise(Mean_Diagnosis_Age = mean(age_at_diagnosis,na.rm=TRUE))

Including units in the column

The final column in this example contains height information (where available) for our patients. Clearly it is important to know what units this is recorded in, but placing the units inside the entries creates issues as we can’t treat the data as numbers.

arrange(messy,height_at_diagnosis)

The stringr package can be used again, and this time a function called str_remove_all which removes all occurrences of a particular string. In particular we want to remove cm. We will need an additional step to convert the column into numeric values

messy %>% 
  mutate(height_at_diagnosis=str_remove_all(height_at_diagnosis, "cm")) %>% 
  mutate(height_at_diagnosis = as.numeric(height_at_diagnosis)) %>% 
  arrange(height_at_diagnosis)

There is usually more than one way of completing a task in R. In this instance, we could also use the str_sub function in stringr to extract a “substring” from each entry in the column. The argument end=-3 specifies that the extraction should end three characters before the end of each string.

messy %>% 
  mutate(height_at_diagnosis=str_sub(height_at_diagnosis, end=-3)) %>% 
    mutate(height_at_diagnosis = as.numeric(height_at_diagnosis)) %>% 
  arrange(height_at_diagnosis)

Final code to clean the data

For reference, here is the final code chunk that can be used to clean the data.

cleaned <- read_tsv("tcga_clinical_MESSY.tsv", na = c("NULL","NA")) %>% 
  mutate(messy, gender = forcats::fct_recode(gender,"MALE"="male"),
       gender = forcats::fct_recode(gender,"FEMALE"="female")) %>% 
    mutate(height_at_diagnosis=str_sub(height_at_diagnosis, end=-3)) %>% 
    mutate(height_at_diagnosis = as.numeric(height_at_diagnosis))
LS0tCnRpdGxlOiAiUiBJbnRyb2R1Y3Rpb24gZm9yIEJpb2xvZ3kiCmF1dGhvcjogIk1hcmsgRHVubmluZyIKZGF0ZTogJ2ByIGZvcm1hdChTeXMudGltZSgpLCAiTGFzdCBtb2RpZmllZDogJWQgJWIgJVkiKWAnCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNzczogc3R5bGVzaGVldHMvc3R5bGVzLmNzcwotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLG1lc3NhZ2U9RkFMU0UpCmBgYAoKCiMgRGVhbGluZyB3aXRoIGRhdGEKClRoZSBbKioqdGlkeXZlcnNlKioqXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykgaXMgYW4gZWNvLXN5c3RlbSBvZiBwYWNrYWdlcyB0aGF0IHByb3ZpZGVzIGEgY29uc2lzdGVudCwgaW50dWl0aXZlIHN5c3RlbSBmb3IgZGF0YSBtYW5pcHVsYXRpb24gYW5kIHZpc3VhbGlzYXRpb24gaW4gUi4KCgohW10oaHR0cHM6Ly9hYmVyZGVlbnN0dWR5Z3JvdXAuZ2l0aHViLmlvL3N0dWR5R3JvdXAvbGVzc29ucy9TRy1UMi1Kb2ludFdvcmtzaG9wL3RpZHl2ZXJzZS5wbmcpCgpBIHNwcmVhZHNoZWV0IHRoYXQgd2Ugd291bGQgbm9ybWFsbHkgYW5hbHlzZSBpbiBFeGNlbCBjYW4gY2FuIGJlIGltcG9ydGVkIGludG8gUiBieSB1c2luZyBhICpmdW5jdGlvbiogZnJvbSB0aGUgcmVhZHIgcGFja2FnZS4KCi0gYHJlYWRfY3N2YAotIGByZWFkX3RzdmAKClRoZSByZWFkeGwgcGFja2FnZSBjYW4gYWxzbyBpbXBvcnQgZmlsZXMgaW4gRXhjZWwncyBvd24gZm9ybWF0CgotIGByZWFkX3hsc2AKLSBgcmVhZF94bHN4YAoKV2Ugd2lsbCB1c2UgdGhlIGZpbGUgYHRjZ2FfY2xpbmljYWxfQ0xFQU5FRC50c3ZgICgxMU1CKSBhcyBhbiBleGFtcGxlLiBUaGlzIGZpbGUgY29udGFpbnMgaW5mb3JtYXRpb24gYWJvdXQgYmlvbG9naWNhbCBzYW1wbGVzIHRoYXQgd2VyZSBpbmNsdWRlZCBhcyBwYXJ0IG9mIHRoZSBUQ0dBIChUaGUgQ2FuY2VyIEdlbm9tZSBBdGxhcykgcHJvamVjdCAtIGFsbCB0dW1vdXIgdHlwZXMuCgpJZiB5b3UgZG8gbm90IGhhdmUgdGhpcyBmaWxlLCBpdCBjYW4gYmUgZG93bmxvYWRlZCB3aXRoIHRoZSBmb2xsb3dpbmcgY29kZS4gTi5CLiB0aGUgY29kZSB3aWxsIGZpcnN0IGNoZWNrIGlmIHRoZSBmaWxlIGlzIHByZXNlbnQgb3Igbm90LgoKYGBge3J9CmlmKCFmaWxlLmV4aXN0cygidGNnYV9jbGluaWNhbF9DTEVBTkVELnRzdiIpKSBkb3dubG9hZC5maWxlKHVybCA9ICJodHRwczovL3NiYy5zaGVmLmFjLnVrL3I0YmlvbC90Y2dhX2NsaW5pY2FsX0NMRUFORUQudHN2IiwgCiAgICAgICAgICAgICAgZGVzdGZpbGUgPSAidGNnYV9jbGluaWNhbF9DTEVBTkVELnRzdiIpCmBgYAoKCkFzIHRoZSBmaWxlIG5hbWUgZW5kcyBpbiBgLnRzdmAgdGhlIGZ1bmN0aW9uIHdlIHdhbnQgaXMgcHJvYmFibHkgYHJlYWRfdHN2YAoKYGBge3Igd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHJlYWRyKQpjbGluaWNhbCA8LSByZWFkX3RzdigidGNnYV9jbGluaWNhbF9DTEVBTkVELnRzdiIpCmBgYAoKSWYgeW91IGdvdCBhbiBlcnJvciBgY291bGQgbm90IGZpbmQgZnVuY3Rpb24gInJlYWRfdHN2ImAgYXQgdGhlIHByZXZpb3VzIHN0ZXAsIGl0IHdpbGwgYmVjYXVzZSB5b3UgZG9uJ3QgaGF2ZSB0aGUgYHJlYWRyYCBwYWNrYWdlIGluc3RhbGxlZC4gSW4gd2hpY2ggeW91IHdpbGwgbmVlZCB0byBydW4gdGhlIGNvbW1hbmQ6LQoKYGBge3IgZXZhbD1GQUxTRX0KaW5zdGFsbC5wYWNrYWdlcygicmVhZHIiKQpsaWJyYXJ5KHJlYWRyKQpgYGAKClRoaXMgd2lsbCBpbnN0YWxsIHRoZSBgcmVhZHJgIHBhY2thZ2UuIFlvdSB3aWxsIHRoZW4gbmVlZCB0byBydW4gdGhlIGByZWFkX3RzdmAgbGluZSBvZiBjb2RlIGZyb20gYWJvdmUuCgoKCjxkaXYgY2xhc3M9ImluZm9ybWF0aW9uIj4KSWYgeW91IGdldCByZWFsbHkgc3R1Y2sgcmVhZGluZyBkYXRhIGludG8gUiwgeW91IGNhbiB1c2UgdGhlIEltcG9ydCBEYXRhc2V0IG9wdGlvbiBmcm9tIHRoZSBGaWxlIG1lbnUgd2hpY2ggd2lsbCBhbGxvdyB5b3UgdG8gY2hvb3NlIHRoZSBwYXJhbWV0ZXJzIHRvIHJlYWQgdGhlIGRhdGEgaW50ZXJhY3RpdmVseQo8L2Rpdj4KClRoZSBFbnZpcm9ubWVudCBwYW5lbCBvZiBSU3R1ZGlvICh0b3AtcmlnaHQpIHNob3VsZCBzaG93IHRoYXQgYW4gb2JqZWN0IGNhbGxlZCBgY2xpbmljYWxgIGhhcyBiZWVuIGNyZWF0ZWQuIFRoaXMgbWVhbnMgdGhhdCB3ZSBjYW4gc3RhcnQgZG9pbmcgc29tZSBhbmFseXNpcyBvbiB0aGlzIGRhdGFzZXQuIFRoZSBjaG9pY2UgdG8gY2FsbCB0aGUgb2JqZWN0IGBjbGluaWNhbGAgd2FzIG91cnMuIFdlIGNvdWxkIGhhdmUgdXNlZCBhbnkgbmFtZSBpbnN0ZWFkIG9mIGBjbGluaWNhbGAgYnV0IHdlIGNob3NlIHNvbWV0aGluZyB2YWd1ZWx5IGluZm9ybWF0aXZlIGFuZCBtZW1vcmFibGUuCgpUaGUgZGltZW5zaW9ucyBvZiB0aGUgb2JqZWN0IHNob3VsZCBzaG91bGQgYDc3MDYgb2JzLiBvZiA0MjAgdmFyaWFibGVzYC4gVGhpcyBtZWFucyB0aGUgb2JqZWN0IHdlIGhhdmUgY3JlYXRlZCBjb250YWlucyA3NzA2IHJvd3MgYW5kIDQyMCBjb2x1bW5zLiBFYWNoIHJvdyBpcyBhIGRpZmZlcmVudCBvYnNlcnZhdGlvbjsgaW4gdGhpcyBjYXNlIGEgZGlmZmVyZW50IGJpb2xvZ2ljYWwgc2FtcGxlLiBFYWNoIGNvbHVtbiByZWNvcmRzIHRoZSB2YWx1ZSBvZiBhIGRpZmZlcmVudCB2YXJpYWJsZS4gSW4gUidzIHRlcm1pbm9sb2d5LCB3ZSBoYXZlIGp1c3QgY3JlYXRlZCBhIGB0aWJibGVgIHdoaWNoIGlzIGEgc3BlY2lhbCBjYXNlIG9mIHNvbWV0aGluZyBjYWxsZWQgYSBgZGF0YS5mcmFtZWAuIEFzIHdlIHdpbGwgc2VlIHRoZSB0aGUgb2JqZWN0IGNhbiBjb250YWluIGVpdGhlciBudW1iZXJzIG9yIHRleHQgaW4gZWFjaCBjb2x1bW4uCgpSU3R1ZGlvIGhhcyBhIGJyb3dzaW5nIGZ1bmN0aW9uYWxpdHkgYXZhaWxhYmxlIHRoYXQgd2UgY2FuIGFjdGl2YXRlIHVzaW5nIHRoZSBgVmlld2AgZnVuY3Rpb24KCmBgYHtyfQpWaWV3KGNsaW5pY2FsKQpgYGAKCk9ic2VydmF0aW9ucyB0aGF0IGFyZSBtaXNzaW5nIGFyZSBpbmRpY2F0ZWQgYnkgYW4gYE5BYC4gSXQgbWF5IGxvb2sgYXQgZmlyc3QgZ2xhbmNlIHRoYXQgdGhlIHRhYmxlIG1vc3RseSBjb250YWlucyBtaXNzaW5nIGRhdGEuIFRoaXMgaXMgYmVjYXVzZSBzb21lIGNvbHVtbnMgYXJlIG9ubHkgcmVjb3JkZWQgZm9yIHBhcnRpY3VsYXIgdHVtb3VyIHR5cGVzIGFuZCBub3QgYWxsIHR1bW91ciB0eXBlcyByZWNvcmRlZCB0aGUgc2FtZSBpbmZvcm1hdGlvbi4KCiMgRGF0YSBFeHBsb3JhdGlvbgoKV2Ugd2lsbCBub3cgc3RhcnQgdG8gZXhwbG9yZSB0aGUgZGF0YSB3aXRoIGEgY291cGxlIG9mIGRpZmZlcmVudCBxdWVzdGlvbnMgaW4gbWluZDotCgotICoqV2hhdCBhcmUgdGhlIGRpZmZlcmVuY2VzIGluIG1hbGUvZmVtYWxlIHNwbGl0IGJldHdlZW4gZGlmZmVyZW50IHR1bW91ciB0eXBlcz8qKgotICoqSXMgdGhlcmUgYSBkaWZmZXJlbmNlIGluIGRpYWdub3NpcyBhZ2UgaW4gZGlmZmVyZW50IHR1bW91ciB0eXBlcz8qKgoKVGhlIHRhc2tzIHJlcXVpcmVkIHRvIGRvIHRoaXMgd2lsbCBiZSBncmVhdGx5IGhlbHBlZCBieSB0aGUgYGRwbHlyYCBwYWNrYWdlLgoKYGBge3J9CmxpYnJhcnkoZHBseXIpCmBgYAoKQXMgd2l0aCB0aGUgYHJlYWRyYCBwYWNrYWdlIGFib3ZlLCB5b3Ugd2lsbCBuZWVkIHRvIGhhdmUgaW5zdGFsbGVkIHRoZSBgZHBseXJgIHBhY2thZ2UgYmVmb3JlaGFuZC4gCgpgYGB7ciBldmFsPUZBTFNFfQppbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpCgpgYGAKCgojIyBDaG9vc2luZyB3aGF0IGNvbHVtbnMgdG8gYW5hbHlzZQoKVGhlIGBzZWxlY3RgIGZ1bmN0aW9uIGFsbG93cyB1cyB0byBuYXJyb3cgZG93biB0aGUgbnVtYmVyIG9mIHZhcmlhYmxlcyB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBmcm9tIDQyMC4gVGhlIGZpcnN0IGFyZ3VtZW50IGlzIGFsd2F5cyB0aGUgbmFtZSBvZiB0aGUgZGF0YSBmcmFtZS4gVGhlcmUgYXJlIG51bWVyb3VzIGRpZmZlcmVudCB3YXlzIG9mIHNwZWNpZnlpbmcgd2hpY2ggY29sdW1uKHMpIHlvdSB3YW50LCBpbmNsdWRpbmcgdHlwaW5nIHRoZSBuYW1lcyBvZiB0aGUgY29sdW1ucyBvZiBpbnRlcmVzdCAqaW4gZXhhY3RseSB0aGUgc2FtZSB3YXkgdGhhdCB0aGV5IGFwcGVhciBpbiB0aGUgZGF0YSouIExldCdzIGFzc3VtZSB3ZSBhbHJlYWR5IGtub3cgdGhlIG5hbWVzIG9mIGNvbHVtbnMgY29udGFpbmluZyB0dW1vdXIgdHlwZSBhbmQgZ2VuZGVyLgoKPGRpdiBjbGFzcz0iaW5mb3JtYXRpb24iPgpUaGUgZGF0YXNldCB1dGlsaXplcyBhIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiBvZiBnZW5kZXIgYXMgJ21hbGUnIG9yICdmZW1hbGUnLiBJdCBpcyBpbXBvcnRhbnQgdG8gbm90ZSB0aGF0IHRoaXMgY2F0ZWdvcml6YXRpb24gbWF5IG5vdCBmdWxseSBlbmNvbXBhc3MgdGhlIGRpdmVyc2UgcmFuZ2Ugb2YgZ2VuZGVyIGlkZW50aXRpZXMgcmVjb2duaXplZCB0b2RheS4KPC9kaXY+CgpgYGB7cn0KIyNOb3RlIHRoYXQgdGhlIHNwZWxsaW5nIG9mICJ0dW1vciIgaGFzIHRvIGV4YWN0bHkgbWF0Y2ggdGhhdCBmb3VuZCBpbiB0aGUgZGF0YQpzZWxlY3QoY2xpbmljYWwsIAogICAgICAgdHVtb3JfdGlzc3VlX3NpdGUsCiAgICAgICBnZW5kZXIKICAgICAgICkKYGBgCldlIGNhbiByZWFzb25hYmx5IGd1ZXNzIHRoYXQgdGhlIEFnZSBpbmZvcm1hdGlvbiB3b3VsZCBiZSBjb250YWluZWQgaW4gYSBjb2x1bW4gdGhhdCBoYXMgImFnZSIgc29tZXdoZXJlIGluIHRoZSBuYW1lLiBIb3dldmVyLCB0aGUgY29sdW1uIGlzIG5vdCBqdXN0IGNhbGxlZCAiYWdlIiBvciAiQWdlIgoKYGBge3IgZXZhbD1GQUxTRX0Kc2VsZWN0KGNsaW5pY2FsLCBhZ2UpCnNlbGVjdChjbGluaWNhbCwgQWdlKQpgYGAKCldpdGhvdXQgbWFudWFsbHkgZ29pbmcgdGhyb3VnaCB0aGUgY29sdW1ucywgdGhlcmUgYXJlIGEgZmV3ICJoZWxwZXIiIGZ1bmN0aW9ucyB0aGF0IHdlIGNhbiBlbXBsb3kKCmBgYHtyfQpzZWxlY3QoY2xpbmljYWwsIGNvbnRhaW5zKCJhZ2UiKSkKc2VsZWN0KGNsaW5pY2FsLCBjb250YWlucygiYWdlXyIpKQpzZWxlY3QoY2xpbmljYWwsIHN0YXJ0c193aXRoKCJhZ2UiKSkKYGBgCgpVcCB0byBub3cgd2UgaGF2ZSBub3QgY2hhbmdlZCB0aGUgdW5kZXJseWluZyBkYXRhc2V0LiBgc2VsZWN0YCBpcyBzaG93aW5nIHdoYXQgdGhlIGRhdGFzZXQgbG9va3MgbGlrZSBhZnRlciBhcHBseWluZyB0aGUgc3BlY2lmaWVkIHN1YnNldC4gSWYgd2Ugd2FudCB0byBtYWtlIHBlcm1hbmVudCBjaGFuZ2VzIHdlIGNhbiBjcmVhdGUgYSB2YXJpYWJsZQoKYGBge3J9CmFuYWx5c2lzX2RhdGEgPC0gc2VsZWN0KGNsaW5pY2FsLAogICAgICAgICAgICAgICAgICAgICAgICBiY3JfcGF0aWVudF9iYXJjb2RlLAogICAgICAgICAgICAgICAgICAgICAgICB0dW1vcl90aXNzdWVfc2l0ZSwKICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZGVyLAogICAgICAgICAgICAgICAgICAgICAgICBhZ2VfYXRfZGlhZ25vc2lzKQpgYGAKCiMjIFJlc3RyaWN0aW5nIHJvd3MKClRoZSBgc2VsZWN0YCBmdW5jdGlvbiBvbmx5IHBlcmZvcm1zIHRoZSB2ZXJ5IHNwZWNpZmljIHRhc2sgb2YgbGV0dGluZyB5b3UgY2hvb3NlIHdoYXQgY29sdW1ucyB5b3Ugd2FudCB0byBhbmFseXNlLiBBZnRlciB1c2luZyBgc2VsZWN0YCwgb3VyIGRhdGFzZXQgYGFuYWx5c2lzX2RhdGFgIHN0aWxsIGhhcyBhbGwgYHIgbnJvdyhhbmFseXNpc19kYXRhKWAgcm93cy4gCgpUaGUgZnVuY3Rpb24gdG8gY2hvb3NlIG9yIHJlc3RyaWN0IHRvIHRoZSByb3dzIHdlIG1pZ2h0IGJlIGludGVyZXN0ZWQgaW4gaXMgY2FsbGVkIGBmaWx0ZXJgLiBXZSBoYXZlIHRvIHdyaXRlIGEgc2hvcnQgUiBjb21tYW5kIHRvIGNob29zZSB0aGUgcm93cy4gCgplLmcuIGlmIHdlIHdhbnQgb25seSB0aGUgbWFsZSBzYW1wbGVzIHdlIHVzZSB0aGUgZm9sbG93aW5nIGNvZGUuIE5vdGljZSB0aGF0IHR3byAiPSIgc2lnbnMgYXJlIHJlcXVpcmVkLiBJZiB5b3UgdHJ5IGFuZCB1c2UgdGhlIGZ1bmN0aW9uIHdpdGggYSBzaW5nbGUgIj0iIFIgd2lsbCBwcmludCBhIGhlbHBmdWwgaGludC4gCgpgYGB7cn0KZmlsdGVyKGFuYWx5c2lzX2RhdGEsIGdlbmRlciA9PSAiTUFMRSIpCmBgYAphbmQgdGhlIGZlbWFsZXMKCmBgYHtyfQpmaWx0ZXIoYW5hbHlzaXNfZGF0YSwgZ2VuZGVyID09ICJGRU1BTEUiKQpgYGAKCgpBbiBlcXVpdmFsZW50LCBidXQgc29tZXdoYXQgdW5uZWNlc3NhcnkgZm9yIHRoaXMgZXhhbXBsZSwgc3RhdGVtZW50IHdvdWxkIGJlIHRvIGFzayBmb3Igcm93cyB3aGVyZSB0aGUgZ2VuZGVyIGlzICoqbm90IGVxdWFsKiogdG8gYE1BTEVgCgpgYGB7cn0KZmlsdGVyKGFuYWx5c2lzX2RhdGEsIGdlbmRlciAhPSAiTUFMRSIpCmBgYAoKPGRpdiBjbGFzcz0iaW5mb3JtYXRpb24iPgpJZGVudGlmeWluZyBtYWxlIG9yIGZlbWFsZXMgYmFzZWQgb24gdGhlIGBnZW5kZXJgIGNvbHVtbiBpcyBzaW1wbGlmaWVkIGJ5IHRoZXJlIG9ubHkgYmVpbmcgdHdvIHBvc3NpYmxlIGVudHJpZXMgaW4gdGhpcyBjb2x1bW47IGBNQUxFYCBvciBgRkVNQUxFYC4gSW4gYSByZWFsIGxpZmUgc2l0dWF0aW9uLCBlc3BlY2lhbGx5IGlmIGRhdGEgaGF2ZSBiZWVuIGVudGVyZWQgYnkgaGFuZCwgdGhlcmUgY291bGQgYmUgaW5jb25zaXN0ZW5jaWVzIHRoYXQgcmVxdWlyZSBhY3Rpb24uIFNlZSBhbiBleGFtcGxlIGF0IHRoZSBlbmQgb2YgdGhlIHR1dG9yaWFsLiAKPC9kaXY+CgpXZSBjYW4gYWxzbyByZXN0cmljdCB0aGUgZGF0YSBiYXNlZCBvbiBudW1lcmljIGNvbHVtbnMgYnkgdXNpbmcgYD5gLCBgPGAgZXRjLgoKYGBge3J9CmZpbHRlcihhbmFseXNpc19kYXRhLCBhZ2VfYXRfZGlhZ25vc2lzID4gODApCmBgYAoKCkJ1dCB3aGF0IGlmIHdlIHdhbnQgbWFsZXMgd2l0aCBCcmFpbiB0dW1vdXJzPyBgZHBseXJgIGFsbG93cyB1cyB0byBjb21iaW5lIG1vcmUgdGhhbiBvbmUgY29uZGl0aW9uIGlmIHdlIHNlcGFyYXRlIHRoZW0gd2l0aCBhIGAsYC4gSW4gY29tcHV0aW5nIHRoaXMgaXMga25vd24gYXMgYW4gImFuZCIgc3RhdGVtZW50IGFuZCBvbmx5IHJvd3Mgd2hlcmUgKipib3RoKiogc3RhdGVtZW50cyBhcmUgdHdvIHdpbGwgYmUgc2hvd24uIFRoZSBsaW5lIGNvdWxkIGJlIGV4dGVuZGVkIHRvIGluY2x1ZGUgbW9yZSB0aGFuIHR3byB0ZXN0cyBpZiB3ZSB3YW50ZWQgdG8uIAoKYGBge3J9CmZpbHRlcihhbmFseXNpc19kYXRhLCBnZW5kZXIgPT0gIk1BTEUiLHR1bW9yX3Rpc3N1ZV9zaXRlID09ICJCcmFpbiIpCmBgYAoKCkhvdyBhYm91dCBicmFpbiBvciBsdW5nIHR1bW91cnM/IFVzaW5nIGEgYHxgIHN5bWJvbCBpbnN0ZWFkIG9mIGEgYCxgIGFsbG93cyBmb3IgKmVpdGhlciogb2YgdHdvIChvciBtb3JlKSBjb25kaXRpb25zIHRvIGJlIGBUUlVFYC4gCgoKYGBge3J9CmZpbHRlcihhbmFseXNpc19kYXRhLCB0dW1vcl90aXNzdWVfc2l0ZSA9PSAiQnJhaW4iIHwgdHVtb3JfdGlzc3VlX3NpdGUgPT0gIkx1bmciKQpgYGAKClRvIGFuc3dlciB0aGUgcXVlc3Rpb24gb2YgaG93IG1hbnkgbWFsZXMgLyBmZW1hbGVzIGhhdmUgYSBjZXJ0YWluIHR1bW91ciB0eXBlIHdlIGNvdWxkIG5vdyB1c2UgUiBzdGF0ZW1lbnRzIHN1Y2ggYXM6LQoKYGBge3J9CmZpbHRlcihhbmFseXNpc19kYXRhLCBnZW5kZXIgPT0gIk1BTEUiLHR1bW9yX3Rpc3N1ZV9zaXRlID09ICJCcmFpbiIpCmZpbHRlcihhbmFseXNpc19kYXRhLCBnZW5kZXIgPT0gIkZFTUFMRSIsdHVtb3JfdGlzc3VlX3NpdGUgPT0gIkJyYWluIikKYGBgCgphbmQgbWFrZSBhIG5vdGUgb2YgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW5jbHVkZWQgaW4gdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lLiBIb3dldmVyLCB0aGVyZSBpcyBtdWNoIG1vcmUgZmxleGlibGUgd2F5IG9mIHN1bW1hcmlzaW5nIGRhdGEgaW4gdGhpcyBtYW5uZXIuCgojIyBTdW1tYXJpc2luZwoKQWx0aG91Z2ggdXNlZnVsIGZvciBkYXRhIGV4cGxvcmF0aW9uLCBpdCB3b3VsZCBjbGVhcmx5IGJlIGluZWZmaWNpZW50IHRvIGdldCBnZW5kZXIvdHVtb3VyIHR5cGUgY291bnRzIGluIHRoaXMgd2F5IGFzIHdlIHdvdWxkIGhhdmUgdG8gcmVwZWF0IGZvciBhbGwgY29tYmluYXRpb25zIG9mIHR1bW91ciB0eXBlIGFuZCBnZW5kZXIuIFRoZSBmdW5jdGlvbiBgY291bnRgIGNhbiBub3cgZ2l2ZSB1cyBleGFjdGx5IHdoYXQgd2Ugd2FudC4gVGhlIG91dHB1dCBpcyBnaXZlbiBhcyBhIGB0aWJibGVgLCBzbyB3ZSBjb3VsZCB1c2Ugc29tZSBvZiB0aGUgZnVuY3Rpb25zIHRoYXQgd2UgaGF2ZSBsZWFybnQgYWJvdXQgc28gZmFyIChgc2VsZWN0YCwgYGZpbHRlcmAuLi4pIHRvIGZ1cnRoZXIgbWFuaXB1bGF0ZS4gZS5nLiBvYnRhaW4gdGhlIGNvdW50cyBmb3IganVzdCBCcmFpbi8KCmBgYHtyfQpjb3VudChhbmFseXNpc19kYXRhLCAKICAgICAgdHVtb3JfdGlzc3VlX3NpdGUsCiAgICAgIGdlbmRlcikKYGBgCgpUaGUgYGNvdW50YCBmdW5jdGlvbiBpcyB1c2VmdWwgZm9yIHRhYnVsYXRpbmcgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMsIGJ1dCBmb3Igb3RoZXIgc3VtbWFyeSBzdGF0aXN0aWNzIGEgbW9yZSBnZW5lcmFsIGBzdW1hbXJpc2VgIGZ1bmN0aW9uIGNhbiBiZSB1c2VkLiBUaGlzIGNhbiBiZSB1c2VkIGluIGNvbmp1bmN0aW9uIHdpdGggYmFzaWMgc3VtbWFyeSBmdW5jdGlvbnMgc3VwcG9ydGVkIGJ5IGJhc2UgUi4gQSBzdW1tYXJ5IHN0YXRpc3RpYyBiZWluZyBzb21ldGhpbmcgdGhhdCBjYW4gYmUgYXBwbGllZCB0byBhIHNlcmllcyBvZiBudW1iZXJzIGFuZCBwcm9kdWNlIGEgc2luZ2xlIG51bWJlciBhcyBhIHJlc3VsdC4gZS5nLiB0aGUgYXZlcmFnZSwgbWluaW11bSwgbWF4aW11bSBldGMuCgpgYGB7cn0Kc3VtbWFyaXNlKGFuYWx5c2lzX2RhdGEsIAogICAgICAgICAgQXZlcmFnZSA9IG1lYW4oYWdlX2F0X2RpYWdub3NpcyksCiAgICAgICAgICBtaW4gPSBtaW4oYWdlX2F0X2RpYWdub3NpcyksCiAgICAgICAgICBtYXggPSBtYXgoYWdlX2F0X2RpYWdub3NpcykpCmBgYApIb3dldmVyLCB3ZSBoYXZlIGEgcHJvYmxlbSBkdWUgdG8gbWlzc2luZyB2YWx1ZXMuIElmIFIgc2VlcyBhbmQgbWlzc2luZyB2YWx1ZXMgaW4gYSBjb2x1bW4gaXQgd2lsbCByZXBvcnQgdGhlIG1lYW4sIG1pbmltdW0gb3IgbWF4aW11bSBvZiB0aGF0IGNvbHVtbiBhcyBhIG1pc3NpbmcgdmFsdWUuIEFsdGhvdWdoIHRoaXMgZGVmYXVsdCBiZWhhdmlvdXIgY2FuIGJlIGNoYW5nZWQsIGJlZm9yZSBwcm9jZWVkaW5nIHdlIGNvdWxkIGFsc28gY2hvb3NlIHRvIHJlbW92ZSBhbnkgbWlzc2luZyBvYnNlcnZhdGlvbnMgZnJvbSB0aGUgZGF0YS4gVGhlc2UgYXJlIHJlcHJlc2VudGVkIGJ5IGEgYE5BYCB2YWx1ZSwgd2hpY2ggaXMgYSBzcGVjaWFsIHZhbHVlIGFuZCBub3QgYSBjaGFyYWN0ZXIgbGFiZWwuICAKCmBgYHtyfQpmaWx0ZXIoYW5hbHlzaXNfZGF0YSwgaXMubmEoYWdlX2F0X2RpYWdub3NpcykgfCBpcy5uYSh0dW1vcl90aXNzdWVfc2l0ZSkpCmBgYAoKV2Ugd2FudCB0aGUgb3Bwb3NpdGUgb2YgdGhlIGFib3ZlOyB3aGVyZSB0aGUgYWdlIG9mIGRpYWdub3NpcyBhbmQgdHVtb3VyIHNpdGUgaXMgKipub3QqKiBtaXNzaW5nLiAKCmBgYHtyfQphbmFseXNpc19kYXRhIDwtIGZpbHRlcihhbmFseXNpc19kYXRhLCAhaXMubmEoYWdlX2F0X2RpYWdub3NpcyksICFpcy5uYSh0dW1vcl90aXNzdWVfc2l0ZSkpCmBgYAoKVGhlIHN1bW1hcnkgd2lsbCBub3cgd29yayBhcyBleHBlY3RlZC4KCmBgYHtyfQpzdW1tYXJpc2UoYW5hbHlzaXNfZGF0YSwgCiAgICAgICAgICBBdmVyYWdlID0gbWVhbihhZ2VfYXRfZGlhZ25vc2lzKSwKICAgICAgICAgIG1pbiA9IG1pbihhZ2VfYXRfZGlhZ25vc2lzKSwKICAgICAgICAgIG1heCA9IG1heChhZ2VfYXRfZGlhZ25vc2lzKSkKYGBgCgpUaGlzIG1pZ2h0IG5vdCBiZSB3aGF0IHdlIHdhbnQgaW4gYWxsIGNpcmN1bXN0YW5jZXMsIGFzIHRoZSBzdGF0aXN0aWNzIGNhbiBhbHNvIGJlIGNhbGN1bGF0ZWQgb24gYSBwZXItdHVtb3VyIHNpdGUgYmFzaXMgdXNpbmcgYGRwbHlyYHMgYGdyb3VwX2J5YCBmdW5jdGlvbi4gCgpgYGB7cn0KCmRhdGFfZ3JvdXBlZCA8LSBncm91cF9ieShhbmFseXNpc19kYXRhLCB0dW1vcl90aXNzdWVfc2l0ZSkKc3VtbWFyaXNlKGRhdGFfZ3JvdXBlZCxBdmVyYWdlID0gbWVhbihhZ2VfYXRfZGlhZ25vc2lzKSwKICAgICAgICAgIG1pbiA9IG1pbihhZ2VfYXRfZGlhZ25vc2lzKSwKICAgICAgICAgIG1heCA9IG1heChhZ2VfYXRfZGlhZ25vc2lzKSApCmBgYAoKIyMgU29ydGluZyAoYXJyYW5naW5nKQoKV2UgaGF2ZSBwcmV2aW91c2x5IHVzZWQgYGZpbHRlcmAgdG8gcmVzdHJpY3QgdGhlIHJvd3MgdGhhdCB3ZSBhcmUgaW50ZXJlc3RlZCBpbi4gUmF0aGVyIHRoYW4ganVzdCBhbmFseXNpbmcgdGhlIG1hbGUgb3IgZmVtYWxlIHBhdGllbnRzIChmb3IgZXhhbXBsZSksIHdlIG1pZ2h0IGFsc28gd2FudCB0aGUgcm93cyBpbiBvdXIgdGFibGUgdG8gYmUgb3JkZXJlZCBhY2NvcmRpbmcgdG8gdGhlIGBnZW5kZXJgIGNvbHVtbi4gCgpgYGB7cn0KYXJyYW5nZShhbmFseXNpc19kYXRhLCBnZW5kZXIpCmBgYApXZSBjYW4gYWxzbyBhcnJhbmdlIGJ5IGNvbHVtbnMgY29udGFpbmluZyBudW1lcmljIHZhbHVlcyBpbiBlaXRoZXIgYXNjZW5kaW5nICh0aGUgZGVmYXVsdCkgb3IgZGVzY2VuZGluZyBvcmRlci4KCmBgYHtyfQphcnJhbmdlKGFuYWx5c2lzX2RhdGEsIGFnZV9hdF9kaWFnbm9zaXMpCiMjIFVzZSBhIGRlc2NlbmRpbmcgb3JkZXIKYXJyYW5nZShhbmFseXNpc19kYXRhLCBkZXNjKGFnZV9hdF9kaWFnbm9zaXMpKQpgYGAKTGlrZSBob3cgc29ydGluZyB3b3JrcyBpbiBFeGNlbCwgd2UgY2FuIGFsc28gdXNlIG11dGxpcGxlIGNvbHVtbnMgZm9yIHNvcnRpbmcuIGUuZy4gaWYgd2Ugd2FudCBvcmRlcmluZyBieSBkaWFnbm9zaXMgYWdlIGZvciBlYWNoIHR1bW91ciB0eXBlIHNlcGFyYXRlbHkuCgpgYGB7cn0KYXJyYW5nZShhbmFseXNpc19kYXRhLCB0dW1vcl90aXNzdWVfc2l0ZSwgYWdlX2F0X2RpYWdub3NpcykKYGBgCgoKIyMgV29ya2Zsb3dzIGFuZCAicGlwaW5nIgoKU28gZmFyIHdlIGhhdmUgdXNlZCBzZXZlcmFsIG9wZXJhdGlvbnMgaW4gaXNvbGF0aW9uLiBIb3dldmVyLCB0aGUgcmVhbCBqb3kgKD8pIG9mIGBkcGx5cmAgaXMgaG93IGRpZmZlcmVudCBvcGVyYXRpb25zIGNhbiBiZSBjaGFpbmVkIHRvZ2V0aGVyLiBMZXRzIHNheSB3ZSBqdXN0IHdhbnRlZCBmZW1hbGUgdHVtb3Vycy4KCmBgYHtyfQpmaWx0ZXIoYW5hbHlzaXNfZGF0YSwgZ2VuZGVyID09ICJGRU1BTEUiKQpgYGAKCgoKT3VyIG5leHQgc3RlcCBjb3VsZCBiZSB0byByZW1vdmUgdGhlIGBnZW5kZXJgIGNvbHVtbiBzaW5jZSBpdCBpcyBzb21ld2hhdCByZWR1bmRhbnQuCgpgYGB7cn0KYW5hbHlzaXNfZGF0YTIgPC0gZmlsdGVyKGFuYWx5c2lzX2RhdGEsIGdlbmRlciA9PSAiRkVNQUxFIikKc2VsZWN0KGFuYWx5c2lzX2RhdGEyLCB0dW1vcl90aXNzdWVfc2l0ZSwgYWdlX2F0X2RpYWdub3NpcykKIyMgb3IgCiMjIHNlbGVjdChhbmFseXNpc19kYXRhMiwgLWdlbmRlcikKYGBgCgpUaGUgY29kZSB3b3VsZCBxdWlja2x5IGdldCBjdW1iZXJzb21lIGlmIHdlIHdhbnRlZCB0byBpbmNsdWRlIGFkZGl0aW9uYWwgc3RlcHMgc3VjaCBhcyByZW1vdmluZyBgTkFgIHZhbHVlcy4gQW4gYWx0ZXJuYXRpdmUgYXBwcm9hY2ggY2FsbGVkICJwaXBpbmciIGlzIHJlY29tbWVuZGVkIGFuZCBhY3RpdmF0ZWQgYnkgYWRkaW5nIGAlPiVgIGF0IHRoZSBlbmQgb2YgYSBsaW5lLiBUaGlzIHRlbGxzIFIgdG8gdXNlIHRoZSBvdXRwdXQgb2YgdGhlIGN1cnJlbnQgbGluZSBhcyB0aGUgZmlyc3QgYXJndW1lbnQgb24gdGhlIG5leHQgbGluZS4gSW4gdGhpcyBjdXJyZW50IGV4YW1wbGUgaXQgbWVhbnMgd2UgZG9uJ3QgbmVlZCB0byBzcGVjaWZ5IHdoaWNoIGRhdGEgZnJhbWUgdGhhdCBgc2VsZWN0YCB1c2VzIGFzIGlucHV0IC0gaXQgd2lsbCB1c2UgdGhlIGRhdGEgZnJhbWUgY3JlYXRlZCBieSB0aGUgYGZpbHRlcmAgaW4gdGhlIHByZXZpb3VzIGxpbmUuIFRoZSBjb2RlIHdyaXR0ZW4gdXNpbmcgYCU+JWAgaXMgbW9yZSBjb25jaXNlLgoKCmBgYHtyfQpmaWx0ZXIoYW5hbHlzaXNfZGF0YSwgZ2VuZGVyID09ICJGRU1BTEUiKSAlPiUgIyMgYW5kIHRoZW4uLi4KICBzZWxlY3QodHVtb3JfdGlzc3VlX3NpdGUsIGFnZV9hdF9kaWFnbm9zaXMpICMjICU+JSBhbmQgdGhlbi4uLgpgYGAKCjxkaXYgY2xhc3M9ImluZm9ybWF0aW9uIj4KVGhlIGAlPiVgIG9wZXJhdGlvbiBiZWNvbWVzIGF2YWlsYWJsZSB3aGVuIHlvdSBsb2FkIGBkcGx5cmAuIElmIHlvdSB3aXNoIHRvIHVzZSBwaXBpbmcgb3V0c2lkZSBvZiBgZHBseXJgIHRoZXJlIGlzIGFsc28gYSAiYmFzZSIgZXF1aXZhbGVudCBgfD5gIHRoYXQgZG9lc24ndCByZXF1aXJlIGFueSBsaWJyYXJpZXMgdG8gYmUgbG9hZGVkCmBgYHtyfQpmaWx0ZXIoYW5hbHlzaXNfZGF0YSwgZ2VuZGVyID09ICJGRU1BTEUiKSB8PiAjIyBhbmQgdGhlbi4uLgogIHNlbGVjdCh0dW1vcl90aXNzdWVfc2l0ZSwgYWdlX2F0X2RpYWdub3NpcykgIyMgfD4gYW5kIHRoZW4uLi4KYGBgCjwvZGl2PgoKV2UgcmVjZW50bHkgY3JlYXRlZCBhIHN1bW1hcnkgdGFibGUgZm9yIGVhY2ggdHVtb3VyIHR5cGUgZ2l2aW5nIHRoZSBhdmVyYWdlLCBtaW5pbXVtIGFuZCBtYXhpbXVtIG9mIGRpYWdub3NpcyBhZ2UuIFRoaXMgY2FuIGJlIHJlcGxpY2F0ZWQgdXNpbmcgYCU+JWAgYW5kIGFuIGV4dHJhIHNvcnRpbmcgc3RlcCBhZGRlZCB0byB0aGUgZW5kLgoKYGBge3J9Cmdyb3VwX2J5KGFuYWx5c2lzX2RhdGEsIHR1bW9yX3Rpc3N1ZV9zaXRlKSAlPiUgCnN1bW1hcmlzZShBdmVyYWdlID0gbWVhbihhZ2VfYXRfZGlhZ25vc2lzKSwKICAgICAgICAgIG1pbiA9IG1pbihhZ2VfYXRfZGlhZ25vc2lzKSwKICAgICAgICAgIG1heCA9IG1heChhZ2VfYXRfZGlhZ25vc2lzKSkgJT4lIAogIGFycmFuZ2UoQXZlcmFnZSkKYGBgCgoKIyMgT3ZlcnZpZXcgb2YgcGxvdHRpbmcKCk91ciByZWNvbW1lbmRpbmcgd2F5IG9mIGNyZWF0aW5nIHBsb3RzIGluIFJTdHVkaW8gaXMgdG8gdXNlIHRoZSBgZ2dwbG90MmAgcGFja2FnZSAtIGVzcGVjaWFsbHkgYXMgaXQgaW50ZXJhY3RzIHdlbGwgd2l0aCBgZHBseXJgIGFuZCBvdGhlciBgdGlkeXZlcnNlYCBwYWNrYWdlcy4KCmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCmBgYAoKQSBjb3VwbGUgb2YgdXNlZnVsIHJlZmVyZW5jZXMgYXJlIGdpdmVuIGhlcmUtCgotIFtnZ3Bsb3QyIHJlZmVyZW5jZSBndWlkZV0oaHR0cHM6Ly9wb3NpdC5jby93cC1jb250ZW50L3VwbG9hZHMvMjAyMi8xMC9kYXRhLXZpc3VhbGl6YXRpb24tMS5wZGYpCi0gW0Zsb3djaGFydCBmb3IgZGVjaWRpbmcgb24gd2hhdCBncmFwaCB0eXBlIHRvIHVzZV0oaHR0cHM6Ly93d3cuZGF0YS10by12aXouY29tLykKCgpUaGUgZ2VuZXJhbCBwcmluY2lwbGUgb2YgY3JlYXRpbmcgYSBwbG90IGlzIHRoZSBzYW1lIHJlZ2FyZGxlc3Mgb2Ygd2hhdCBraW5kIG9mIHBsb3Qgd2Ugd2FudCB0byBtYWtlCgotIHNwZWNpZnkgdGhlIGBkYXRhIGZyYW1lYCBjb250YWluaW5nIHRoZSBkYXRhIHdlIHdhbnQgdG8gcGxvdAotIHNwZWNpZnkgd2hpY2ggY29sdW1ucyBpbiB0aGF0IGRhdGEgZnJhbWUgd2Ugd2FudCB0byB1c2UgZm9yIHZhcmlvdXMgYWVzdGhldGljIGFzcGVjdHMgb2YgdGhlIHBsb3QKLSBkZWZpbmUgdGhlIHR5cGUgb2YgcGxvdCB3ZSB3YW50Ci0gYXBwbHkgYW55IGFkZGl0aW9uYWwgZm9ybWF0IGNoYW5nZXMKCkEgYmFyIHBsb3Qgd291bGQgYmUgYSBuYXR1cmFsIGNob2ljZSBmb3Igc2hvd2luZyB0aGUgY291bnRzIG9mIG1hbGUgLyBmZW1hbGUgc2FtcGxlcy4gVGhlIGBnZW9tX2JhcmAgcGxvdCB3aWxsIGF1dG9tYXRpY2FsbHkgY291bnQgaG93IG1hbnkgb2NjdXJyZW5jZXMgdGhlcmUgYXJlIGZvciBlYWNoIHZhbHVlLgoKYGBge3J9CmdncGxvdChhbmFseXNpc19kYXRhLCBhZXMoeCA9IGdlbmRlcikpICsgZ2VvbV9iYXIoKQpgYGAKCk51bWVyaWNhbCBkYXRhIGNhbiBiZSB2aXN1YWxpc2VkIHVzaW5nIGEgZGVuc2l0eSBwbG90IG9yIGhpc3RvZ3JhbS4gVGhlIGRlbnNpdHkgaXMgYXV0b21hdGljYWxseSBjYWxjdWxhdGVkIGFuZCBkaXNwbGF5ZWQgb24gdGhlIHktYXhpcy4KCmBgYHtyfQpnZ3Bsb3QoYW5hbHlzaXNfZGF0YSwgYWVzKHggPSBhZ2VfYXRfZGlhZ25vc2lzKSkgKyBnZW9tX2RlbnNpdHkoKQpgYGAKCgpJbiBvcmRlciB0byBjb21wYXJlIHRoZSBhZ2UgZGlzdHJpYnV0aW9ucyBvZiBkaWZmZXJlbnQgdHVtb3VyIHR5cGVzIHdlIGNhbiBhbHNvIGltYWdpbmUgdGhpcyBiZWluZyBkaXNwbGF5ZWQgYXMgYSBzZXJpZXMgb2YgYm94cGxvdHMgd2l0aAoKLSB0aGUgYWdlIHZhcmlhYmxlIG9uIHRoZSB5LWF4aXMKLSB0aGUgdHlwZSBvZiB0dW1vdXIgb24gdGhlIHgtYXhpcwoKdGhpcyBjYW4gYmUgdHJhbnNsYXRlZCBpbnRvIGBnZ3Bsb3QyYCBsYW5ndWFnZSBhcyBmb2xsb3dzIC0KCmBgYHtyfQpnZ3Bsb3QoYW5hbHlzaXNfZGF0YSwgYWVzKHggPSB0dW1vcl90aXNzdWVfc2l0ZSwgeSA9IGFnZV9hdF9kaWFnbm9zaXMpKSArIGdlb21fYm94cGxvdCgpCmBgYAoKQSBkaXNhZHZhbnRhZ2Ugb2YgdGhlIGJveHBsb3QgaXMgdGhhdCBpdCBvbmx5IGdpdmVzIGEgdmVyeSBjcnVkZSBzdW1tYXJ5IG9mIHRoZSBkYXRhLiBJdCBjYW4gYmUgbWlzbGVhZGluZyB3aGVuIGFwcGxpZWQgdG8gZGF0YSB3aXRoIGZldyBvYnNlcnZhdGlvbnMgYW5kIGlzIG9mdGVuIHByZWZlcmFibGUgdG8gYWRkIGluZGl2aWR1YWwgZGF0YSBwb2ludHMKCmBgYHtyfQpnZ3Bsb3QoYW5hbHlzaXNfZGF0YSwgYWVzKHggPSB0dW1vcl90aXNzdWVfc2l0ZSwgeSA9IGFnZV9hdF9kaWFnbm9zaXMpKSArIGdlb21fYm94cGxvdCgpICsgZ2VvbV9qaXR0ZXIod2lkdGg9MC4xKQpgYGAKQWRkaW5nIHNvbWUgY29sb3VyIHRvIHRoZSBwbG90IGNhbiBiZSBhY2hpZXZlZCBieSBhZGRpbmcgYSBgZmlsbGAgYWVzdGhldGljIGFuZCBzcGVjaWZ5aW5nIHdoYXQgY29sdW1uIHRvIG1hcCB0aGUgY29sb3VycyB0b28uIEEgY29sb3VyIHBhbGV0dGUgaXMgYXV0b21hdGljYWxseSBjaG9zZW4sIGJ1dCBjYW4gYmUgY2hhbmdlZCBhZnRlcndhcmRzIGlmIHdlIHdpc2guCgpgYGB7cn0KZ2dwbG90KGFuYWx5c2lzX2RhdGEsIGFlcyh4ID0gdHVtb3JfdGlzc3VlX3NpdGUsIHkgPSBhZ2VfYXRfZGlhZ25vc2lzLCBmaWxsID0gdHVtb3JfdGlzc3VlX3NpdGUpKSArIGdlb21fYm94cGxvdCgpICsgZ2VvbV9qaXR0ZXIod2lkdGg9MC4xKQpgYGAKQWRkaW5nIHRoZSBgZmlsbGAgYWVzdGhldGljIGZvciB0aGUgZGVuc2l0eSBwbG90IGNhbiBiZSB1c2VkIHRvIHNob3cgYSBzZXBhcmF0ZSBjdXJ2ZSBmb3IgZWFjaCB0dW1vdXIgdHlwZS4gCgpgYGB7cn0KIyMgYWxwaGEgb2YgMC41IHVzZWQgdG8gbWFrZSB0aGUgY3VydmVzIHRyYW5zcGFyZW50CmdncGxvdChhbmFseXNpc19kYXRhLCBhZXMoeCA9IGFnZV9hdF9kaWFnbm9zaXMsIGZpbGwgPSB0dW1vcl90aXNzdWVfc2l0ZSkpICsgZ2VvbV9kZW5zaXR5KGFscGhhPTAuNSkKYGBgCkFub3RoZXIgdXNlZnVsIHRlY2huaXF1ZSBmb3Igc3BsaXR0aW5nIHRoZSBwbG90cyBiYXNlZCBvbiBhIHZhcmlhYmxlIGlzIHRvIHVzZSB0aGUgYGZhY2V0X3dyYXBgIGZ1bmN0aW9uIHRoYXQgd2lsbCBnaXZlIGEgZ3JpZCBvZiBwbG90cy4gRm9yIGluc3RhbmNlIHdlIGNhbiBzaG93IG1hbGUvZmVtYWxlIGNvdW50cyBmb3IgZWFjaCB0dW1vdXIgdHlwZSBzZXBhcmF0ZWx5LgoKYGBge3J9CmdncGxvdChhbmFseXNpc19kYXRhLCBhZXMoeCA9IGdlbmRlcixmaWxsPWdlbmRlcikpICsgZ2VvbV9iYXIoKSArIGZhY2V0X3dyYXAofnR1bW9yX3Rpc3N1ZV9zaXRlKQpgYGAKCkJ5IGNvbWJpbmluZyBhbGwgdGhlIHRlY2huaXF1ZXMgd2UgaGF2ZSBzZWVuIHdlIGNhbiBjb21wYXJlIHRoZSBkaWFnbm9zaXMgYWdlIGJldHdlZW4gbWFsZXMgYW5kIGZlbWFsZXM7IHNlcGFyYXRlbHkgZm9yIGVhY2ggdHVtb3VyIHR5cGUuCgpgYGB7cn0KZ2dwbG90KGFuYWx5c2lzX2RhdGEsIGFlcyh4ID1nZW5kZXIsIHkgPSBhZ2VfYXRfZGlhZ25vc2lzLCBmaWxsID0gZ2VuZGVyKSkgKyBnZW9tX2JveHBsb3QoKSArIGdlb21faml0dGVyKHdpZHRoPTAuMSkgKyBmYWNldF93cmFwKH50dW1vcl90aXNzdWVfc2l0ZSkKYGBgCgojIENoYWxsZW5nZXMgb2YgIm1lc3N5IiBkYXRhCgpSZWFsLWxpZmUgZGF0YSBhcmUgb2Z0ZW4gbGVzcyBzdHJhaWdodGZvcndhcmQgdG8gZGVhbCB3aXRoIHRoYW4gdGhlICJjbGVhbmVkIiBkYXRhc2V0IHByZXNlbnRlZCBoZXJlLiBEZXNwaXRlIHRoZSBtYW55IGhpZ2gtdGhyb3VnaHB1dCB0ZWNobm9sb2dpZXMgdGhhdCBhcmUgdXNlZCBmb3Igc2NpZW50aWZpYyBpbnZlc3RpZ2F0aW9uLCB0aGVyZSBpcyBpbmV2aXRhYmx5IGEgc3ByZWFkc2hlZXQocykgbmVlZGVkIHRvIGRlc2NyaWJlIHRoZSBleHBlcmltZW50YWwgc2V0dXAgYW5kIHRoaXMgaXMgdHlwaWNhbGx5IGVudGVyZWQgbWFudWFsbHkuCgpTby1jYWxsZWQgIkRhdGEgV3JhbmdsaW5nIiBpcyBhIGNydWNpYWwgYW5kIHRpbWUtY29uc3VtaW5nIHBhcnQgb2YgdGhlIGFuYWx5c2lzIHByb2Nlc3MgdGFraW5nIDgwJSBvZiBhbmFseXNpcyB0aW1lIGJ5IHNvbWUgZXN0aW1hdGVzLiBIYWRsZXkgV2lja2hhbSwgQ2hpZWYgU2NpZW50aXN0IGF0IFBvc2l0IGFuZCBsZWFkIGF1dGhvciBvZiBgZ2dwbG90MmAgbGlrZW5zIHRpZHkgYW5kIG1lc3N5IGRhdGEgdG8gTGVvIFRvbHN0b3kncyBxdW90ZSBhYm91dCBmYW1pbGllczotCgo+IEhhcHB5IGZhbWlsaWVzIGFyZSBhbGwgYWxpa2U7IGV2ZXJ5IHVuaGFwcHkgZmFtaWx5IGlzIHVuaGFwcHkgaW4gaXRzIG93bgp3YXkKCgo+IExpa2UgZmFtaWxpZXMsIHRpZHkgZGF0YXNldHMgYXJlIGFsbCBhbGlrZSBidXQgZXZlcnkgbWVzc3kgZGF0YXNldCBpcyBtZXNzeSBpbiBpdHMgb3duIHdheS4gCgpBIGNvbXByZWhlbnNpdmUgZ3VpZGUgdG8gdGhlIGlzc3VlcyBzdXJyb3VuZGluZyBkYXRhIGVudHJ5IHZpYSBzcHJlYWRzaGVldHMsIGFuZCBob3cgdG8gYXZvaWQgdGhlbSwgaXMgZ2l2ZW4gYnkgRGF0YSBDYXJwZW50cnkuCgotIFtEYXRhIENhcnBlbnRyeSBTcHJlYWRzaGVldHMgbGVzc29uXShodHRwczovL2RhdGEtbGVzc29ucy5naXRodWIuaW8vZ2FwbWluZGVyLXNwcmVhZHNoZWV0LykKCkhvd2V2ZXIsIGZvciBwdWJsaWMgZGF0YSB0aGF0IHdlIGhhdmUgbm8gY29udHJvbCBvdmVyIHdlIG9mdGVuIGhhdmUgbm8gY2hvaWNlIGJ1dCB0byBjbGVhbiB0aGUgZGF0YSBvdXJzZWx2ZXMuIFdlIGhhdmUgaW50ZW50aW9uYWxseSBjcmVhdGVkIGFuIGFsdGVybmF0aXZlIGRhdGFzZXQgd2l0aCBhIGZldyBpbnRlbnRpb25hbCBpc3N1ZXMgdG8gaWxsdXN0cmF0ZSB0aGUgY2xlYW5pbmcgcHJvY2Vzcy4gVGhlIGZvbGxvd2luZyBjb2RlIHdpbGwgZG93bmxvYWQgdGhlIGRhdGEgaWYgeW91IGRvIG5vdCBoYXZlIGl0IGFscmVhZHkuCgpgYGB7cn0KaWYoIWZpbGUuZXhpc3RzKCJ0Y2dhX2NsaW5pY2FsX01FU1NZLnRzdiIpKSBkb3dubG9hZC5maWxlKHVybCA9ICJodHRwczovL3NiYy5zaGVmLmFjLnVrL3I0YmlvbC90Y2dhX2NsaW5pY2FsX01FU1NZLnRzdiIsIAogICAgICAgICAgICAgIGRlc3RmaWxlID0gInRjZ2FfY2xpbmljYWxfTUVTU1kudHN2IikKYGBgCgoKYGBge3J9Cm1lc3N5IDwtIHJlYWRfdHN2KCJ0Y2dhX2NsaW5pY2FsX01FU1NZLnRzdiIpCm1lc3N5CmBgYAoKIyMgV2hpdGVzcGFjZQoKIndoaXRlc3BhY2UiIGlzIHRoZSBhZGRpdGlvbiBvZiBhIGJsYW5rIGNoYXJhY3RlciBvciBzcGFjZSB0byB0aGUgYmVnaW5uaW5nIG9yIGVuZCBvZiB0ZXh0LiBUcmFkaXRpb25hbGx5IGl0IGlzIGEgcHJvYmxlbSBiZWNhdXNlIGl0IHdpbGwgY3JlYXRlIGV4dHJhIGNhdGVnb3JpZXMgaW4geW91ciBkYXRhLiBlLmcuIGBNQUxFYCBhbmQgYE1BTEUgYC4gVGhlIG1lc3N5IGRhdGFzZXQgdGhhdCB5b3UgaGF2ZSBqdXN0IGltcG9ydGVkIGluY2x1ZGVzIHNvbWUgd2hpdGVzcGFjZSBpbiB0aGUgYHR1bW9yX3Rpc3N1ZV9zaXRlYCBjb2x1bW4uIEhvd2V2ZXIsIHRoZSBgcmVhZF90c3ZgIGZ1bmN0aW9uIGF1dG9tYXRpY2FsbHkgaWdub3JlcyB3aGl0ZXNwYWNlIHZhbHVlcyBhcyB0aGUgYHRyaW1fd3NgIGFyZ3VtZW50IG9mIGByZWFkX3RzdmAgaXMgc2V0IHRvIGBUUlVFYCAoc2VlIHRoZSBoZWxwIHBhZ2UgYD9yZWFkX3RzdmApLiAKCmBgYHtyfQptZXNzeV93cyA8LSByZWFkX3RzdigidGNnYV9jbGluaWNhbF9NRVNTWS50c3YiLCAKICAgICAgICAgICAgICAgICAgICAgdHJpbV93cyA9IEZBTFNFKQptZXNzeV93cwpjb3VudChtZXNzeV93cyx0dW1vcl90aXNzdWVfc2l0ZSkKYGBgCgpUaGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUgbm93IGNvbnRhaW5zIHR3byBhcHBhcmVudGx5IGlkZW50aWNhbCBjYXRlZ29yaWVzIGZvciBgQmxhZGRlcmAuIEhvd2V2ZXIsIHdpdGggdGhlIHVzZSBvZiB0aGUgYG5jaGFyYCBmdW5jdGlvbiwgd2hpY2ggY291bnRzIHRoZSBudW1iZXIgb2YgY2hhcmFjdGVycywgd2UgY2FuIHNlZSB0aGF0IGV4dHJhIHNwYWNlcyBtdXN0IGJlIGluY2x1ZGVkLgoKYGBge3J9CmNvdW50KG1lc3N5X3dzLHR1bW9yX3Rpc3N1ZV9zaXRlKSAlPiUgCiAgbXV0YXRlKCJMZW5ndGhfb2ZfTGFiZWwiID0gbmNoYXIodHVtb3JfdGlzc3VlX3NpdGUpKQpgYGAKCkNsZWFybHkgd2UgY291bGQgaGF2ZSB1c2VkIHRoZSBkZWZhdWx0IHNldHRpbmdzIGZvciBgcmVhZF90c3ZgIGFuZCB0aGUgcHJvYmxlbSB3b3VsZCBub3QgaGF2ZSBvY2N1cnJlZC4gT3RoZXJ3aXNlLCBpdCBpcyB1c2VmdWwgdG8ga25vdyBhYm91dCB0aGUgYHN0cmluZ3JgIHBhY2thZ2UgdGhhdCBjb250YWlucyBtYW55IHVzZWZ1bCBmdW5jdGlvbnMgZm9yIGNsZWFuaW5nIGNoYXJhY3RlciBkYXRhLgoKLSBbVGhlIHN0cmluZ3IgcGFja2FnZV0oaHR0cHM6Ly9zdHJpbmdyLnRpZHl2ZXJzZS5vcmcvKQoKRm9yIHRoZSBleGFtcGxlIG9mIHJlbW92aW5nIHdoaXRlc3BhY2Ugd2UgY2FuIHVzZSB0aGUgYHN0cl90cmltYCBmdW5jdGlvbiBjb21iaW5lZCB3aXRoIGEgYG11dGF0ZWAuIFRoaXMgd2lsbCByZXBsYWNlIGFsbCB0aGUgd2hpdGVzcGFjZSBpbiB0aGUgYHR1bW9yX3Rpc3N1ZV9zaXRlYCBjb2x1bW4gYW5kIG92ZXJ3cml0ZSB0aGUgY29sdW1uLiBJZiB3ZSByZXBlYXQgYSBjb3VudCBhZnRlcndhcmRzIHdlIHNlZSBvbmx5IHRoZSB1bmlxdWUgZW50cmllcyB0aGF0IHdlIGV4cGVjdC4KCmBgYHtyfQpsaWJyYXJ5KHN0cmluZ3IpCm11dGF0ZShtZXNzeV93cywgdHVtb3JfdGlzc3VlX3NpdGUgPSBzdHJfdHJpbSh0dW1vcl90aXNzdWVfc2l0ZSkpICU+JQogIGNvdW50KHR1bW9yX3Rpc3N1ZV9zaXRlKQpgYGAKCiMjIEluY29uc2lzdGVudCBjb2Rpbmcgb2YgdmFyaWFibGVzCgpVbmZvcnR1bmF0ZWx5IHRoZSBgdHVtb3JfdGlzc3VlX3NpdGVgIGNvbHVtbiBpcyBub3QgdGhlIG9ubHkgb25lIHdpdGggaXNzdWUgdGhhdCBuZWVkIGZpeGluZyB3aXRoIHRoZXNlIGRhdGEuIElmLCBhcyBiZWZvcmUsIHdlIHRyeSBhbmQgcGxvdCB0aGUgbnVtYmVyIG9mIG1hbGVzL2ZlbWFsZXMgaW4gdGhlIGRhdGFzZXQgd2UgZ2V0IGEgc3VycHJpc2UuCgpgYGB7cn0KZ2dwbG90KG1lc3N5LCBhZXMoeCA9IGdlbmRlcikpICsgZ2VvbV9iYXIoKQpgYGAKClRoZXJlIGlzIG5vIGRpZmZlcmVudGlhdGlvbiBiZXR3ZWVuIGBmZW1hbGVgIGFuZCBgRkVNQUxFYCBvciBgbWFsZWAgYW5kIGBNQUxFYC4gV2hpbHN0IHdlIGNhbiBpbnR1aXRpdmVseSBkZWNpZGUgdGhhdCB0aGVzZSByZXByZXNlbnQgdGhlIHNhbWUgdmFsdWUsIHRoZXkgZG8gbm90IGdldCBhdXRvbWF0aWNhbGx5IGNvbWJpbmVkIGluIFIuIFRoZSBjb25zZXF1ZW5jZSBiZWluZyB0aGF0IGF0dGVtcHRzIHRvIGlkZW50aWZ5IGFsbCB0aGUgbWFsZSBwYXRpZW50cyB3aWxsIHJlcXVpcmUgc29tZSBjYXJlZnVsIGNvZGluZy4gVGhlIGV4YW1wbGUgdXNlZCBwcmV2aW91c2x5IHdpbGwgbm93IG5vIGxvbmdlciBpZGVudGlmeSBhbGwgdGhlIGNvcnJlY3QgcGF0aWVudHMuCgpgYGB7cn0KZmlsdGVyKG1lc3N5LCBnZW5kZXIgPT0gIk1BTEUiKQpgYGAKT25lIHNvbHV0aW9uIHdoZW4gZmlsdGVyaW5nIHdvdWxkIGJlIHRvIGFkZCBkaWZmZXJlbnQgY3JpdGVyaWEgdG8gYWNjb3VudCBmb3IgdGhlIGRpZmZlcmVudCBjYXBpdGFsaXNhdGlvbgoKYGBge3J9CmZpbHRlcihtZXNzeSwgZ2VuZGVyID09ICJNQUxFIiB8IGdlbmRlciA9PSAibWFsZSIpCmBgYApIb3dldmVyLCBzaW5jZSB3ZSBrbm93IHRoYXQgdGhlIGVycm9yIGlzIGR1ZSB0byBpbmNvbnNpc3RlbnQgdXNlIG9mIHVwcGVyL2xvd2VyY2FzZSB3ZSBjYW4gdXNlIHRoZSBgc3RyX3RvX3VwcGVyYCBmdW5jdGlvbiBpbiBgc3RyaW5ncmAgdG8gY29udmVydCBhbGwgdmFsdWVzIHRvIHVwcGVyY2FzZS4gT3IgaW5kZWVkIHdlIGNvdWxkIGNvbnZlcnQgYWxsIHRvIGxvd2VyY2FzZSB1c2luZyBgc3RyX3RvX2xvd2VyYCBpZiB3ZSBwcmVmZXJlZC4KCmBgYHtyfQptZXNzeSAlPiUgCiAgbXV0YXRlKGdlbmRlciA9IHN0cl90b191cHBlcihnZW5kZXIpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gZ2VuZGVyKSkgKyBnZW9tX2JhcigpCmBgYApXZSB3b3VsZCBwcm9iYWJseSB3YW50IHRvIGFsc28gbWFrZSB0aGUgY2hhbmdlIHBlcm1hbmVudCBieSBjcmVhdGluZyBhIG5ldyB2YXJpYWJsZQoKYGBge3J9CmNsZWFuZWQgPC0gcmVhZF90c3YoInRjZ2FfY2xpbmljYWxfTUVTU1kudHN2IikgJT4lIAogICAgbXV0YXRlKGdlbmRlciA9IHN0cl90b191cHBlcihnZW5kZXIpKSAKYGBgCgpBIG1vcmUgZ2VuZXJpYyBhcHByb2FjaCB3b3VsZCBiZSB0byB1c2UgdGhlIGBmb3JjYXRzYCBwYWNrYWdlIHRvIHJlcGxhY2UgYWxsIG9jY3VycmVuY2VzIG9mIGBtYWxlYCB3aXRoIGBNQUxFYCBhbmQgdGhlIHNhbWUgZm9yIGZlbWFsZXMuCgotIFtUaGUgZm9yY2F0cyBwYWNrYWdlXShodHRwczovL2ZvcmNhdHMudGlkeXZlcnNlLm9yZy8pCgpUaGUgcGFja2FnZSBhbGxvd3MgdXMgdG8gInJlY29kZSIgZW50cmllcyBpbiBhIGNvbHVtbiB0aGF0IGNvbnRhaW5zIGEgYGZhY3RvcmAuIGkuZS4gY2F0ZWdvcmljYWwuCgpgYGB7cn0KbGlicmFyeShmb3JjYXRzKQptdXRhdGUobWVzc3ksIGdlbmRlciA9IGZvcmNhdHM6OmZjdF9yZWNvZGUoZ2VuZGVyLCJNQUxFIj0ibWFsZSIpLAogICAgICAgZ2VuZGVyID0gZm9yY2F0czo6ZmN0X3JlY29kZShnZW5kZXIsIkZFTUFMRSI9ImZlbWFsZSIpKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHggPSBnZW5kZXIpKSArIGdlb21fYmFyKCkKYGBgCgpUaGUgYXBwcm9hY2ggaXMgbW9yZSBmbGV4aWJsZSB0aGFuIG1lcmVseSBjaGFuZ2luZyB0aGUgY2FzZSwgYXMgaXQgY291bGQgYWxzbyByZXBsYWNlIG90aGVyIHZhbHVlcyBzdWNoIGFzICJtIiBvciAiZiIgaWYgdGhleSBleGlzdGVkLgoKYGBge3IgZXZhbD1GQUxTRX0KIyMgSnVzdCBleGFtcGxlIGNvZGUgaWYgd2Ugd2FudGVkIHRvIHJlcGxhY2UgIm0iIHdpdGggIk1BTEUiCm1lc3N5IDwtIG1lc3N5ICU+JQogIG11dGF0ZShnZW5kZXIgPSBmY3RfcmVjb2RlKGdlbmRlciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICJNQUxFIiA9ICJtYWxlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTUFMRSIgPSAibSIgCiAgKSkKYGBgCgoKIyMgRGlmZmVyZW50IG1lYW5zIG9mIHJlcHJlc2VudGluZyBtaXNzaW5nIHZhbHVlcwoKVGhlcmUgYXJlIG1hbnkgZGlmZmVyZW50IHN0cmF0ZWdpZXMgZm9yIHJlcHJlc2VudGluZyBtaXNzaW5nIGRhdGEuIFRoZSBgcmVhZF90c3ZgIGZ1bmN0aW9uIHNob3VsZCBhdXRvbWF0aWNhbGx5IGRldGVjdCBhbnkgYE5BYCB2YWx1ZXMgaW4gdGhlIHNvdXJjZSBkYXRhc2V0IGFuZCB0cmVhdCB0aGVtIGFwcHJvcHJpYXRlbHkuIEhvd2V2ZXIsIGluIG91ciBtZXNzeSBkYXRhc2V0IHdlIGFsc28gaGF2ZSBgTlVMTGAgdmFsdWVzIChhcyBzZWVuIGJ5IG1ha2luZyBhIGNvdW50IG9mIHRoZSB2YWx1ZXMgaW4gYGFnZV9hdF9kaWFnbm9zaXNgKS4gCgpgYGB7cn0KY291bnQobWVzc3ksIGFnZV9hdF9kaWFnbm9zaXMpICU+JSBhcnJhbmdlKGRlc2MobikpCmBgYAoKQmVjYXVzZSB0aGUgYE5VTExgIHZhbHVlIGlzIHByZXNlbnQgaW4gdGhlIGBhZ2VfYXRfZGlhZ25vc2lzYCBjb2x1bW4sIFIgd2lsbCB0cmVhdC4gdGhlIGVudGlyZSBjb2x1bW4gYXMgY29udGFpbmluZyBjaGFyYWN0ZXJzLiBUaGVyZWZvcmUgd2UgY2Fubm90IHVzZSB0aGUga2luZCBvZiBwbG90cyB3ZSB3b3VsZCBleHBlY3Qgd2l0aCBudW1lcmljIGRhdGEKCmBgYHtyIGV2YWw9RkFMU0V9CmdncGxvdChtZXNzeSwgYWVzKHggPSBhZ2VfYXRfZGlhZ25vc2lzKSkgKyBnZW9tX2hpc3RvZ3JhbSgpCmBgYAoKTGlrZXdpc2Ugd2UgY2FuJ3QgY2FsY3VsYXRlIG51bWVyaWMgc3VtbWFyaWVzOyBhbHRob3VnaCBSIHdpbGwgYXR0ZW1wdCB0byBhbmQgY3JlYXRlIGEgZGF0YSBmcmFtZSBvZiBgTkFgIHZhbHVlcyByYXRoZXIgdGhhbiBnaXZpbmcgYW4gZXJyb3IuCgpgYGB7cn0KICBncm91cF9ieShtZXNzeSwgdHVtb3JfdGlzc3VlX3NpdGUpICU+JSAKICBzdW1tYXJpc2UoTWVhbl9EaWFnbm9zaXNfQWdlID0gbWVhbihhZ2VfYXRfZGlhZ25vc2lzLG5hLnJtPVRSVUUpKQpgYGAKClRoZSBgcmVhZF90c3ZgIGZ1bmN0aW9uIGhhcyB0aGUgYWJpbGl0eSB0byByZXBsYWNlIE5BIHZhbHVlcyB3aGVuIHRoZSBkYXRhIGFyZSBpbXBvcnRlZC4gU3BlY2lmaWNhbGx5LCB0aGUgYG5hYCBhcmd1bWVudCBjYW4gYmUgdXNlZCB0byBkZWZpbmUgd2hhdCB2YWx1ZXMgYXJlIGJlaW5nIHVzZWQgdG8gcmVwcmVzZW50IG1pc3NpbmcuCgpgYGB7cn0KcmVhZF90c3YoInRjZ2FfY2xpbmljYWxfTUVTU1kudHN2IiwgbmEgPSBjKCJOVUxMIiwiTkEiKSkgJT4lIAogIGdncGxvdChhZXMoeCA9IGFnZV9hdF9kaWFnbm9zaXMpKSArIGdlb21faGlzdG9ncmFtKCkKYGBgCgpgYGB7cn0KcmVhZF90c3YoInRjZ2FfY2xpbmljYWxfTUVTU1kudHN2IiwgbmEgPSBjKCJOVUxMIiwiTkEiKSkgJT4lIAogIGdyb3VwX2J5KHR1bW9yX3Rpc3N1ZV9zaXRlKSAlPiUgCiAgc3VtbWFyaXNlKE1lYW5fRGlhZ25vc2lzX0FnZSA9IG1lYW4oYWdlX2F0X2RpYWdub3NpcyxuYS5ybT1UUlVFKSkKYGBgCgojIyBJbmNsdWRpbmcgdW5pdHMgaW4gdGhlIGNvbHVtbgoKVGhlIGZpbmFsIGNvbHVtbiBpbiB0aGlzIGV4YW1wbGUgY29udGFpbnMgaGVpZ2h0IGluZm9ybWF0aW9uICh3aGVyZSBhdmFpbGFibGUpIGZvciBvdXIgcGF0aWVudHMuIENsZWFybHkgaXQgaXMgaW1wb3J0YW50IHRvIGtub3cgd2hhdCB1bml0cyB0aGlzIGlzIHJlY29yZGVkIGluLCBidXQgcGxhY2luZyB0aGUgdW5pdHMgaW5zaWRlIHRoZSBlbnRyaWVzIGNyZWF0ZXMgaXNzdWVzIGFzIHdlIGNhbid0IHRyZWF0IHRoZSBkYXRhIGFzIG51bWJlcnMuCgpgYGB7cn0KYXJyYW5nZShtZXNzeSxoZWlnaHRfYXRfZGlhZ25vc2lzKQpgYGAKVGhlIGBzdHJpbmdyYCBwYWNrYWdlIGNhbiBiZSB1c2VkIGFnYWluLCBhbmQgdGhpcyB0aW1lIGEgZnVuY3Rpb24gY2FsbGVkIGBzdHJfcmVtb3ZlX2FsbGAgd2hpY2ggcmVtb3ZlcyBhbGwgb2NjdXJyZW5jZXMgb2YgYSBwYXJ0aWN1bGFyIHN0cmluZy4gSW4gcGFydGljdWxhciB3ZSB3YW50IHRvIHJlbW92ZSBgY21gLiBXZSB3aWxsIG5lZWQgYW4gYWRkaXRpb25hbCBzdGVwIHRvIGNvbnZlcnQgdGhlIGNvbHVtbiBpbnRvIG51bWVyaWMgdmFsdWVzCgpgYGB7cn0KbWVzc3kgJT4lIAogIG11dGF0ZShoZWlnaHRfYXRfZGlhZ25vc2lzPXN0cl9yZW1vdmVfYWxsKGhlaWdodF9hdF9kaWFnbm9zaXMsICJjbSIpKSAlPiUgCiAgbXV0YXRlKGhlaWdodF9hdF9kaWFnbm9zaXMgPSBhcy5udW1lcmljKGhlaWdodF9hdF9kaWFnbm9zaXMpKSAlPiUgCiAgYXJyYW5nZShoZWlnaHRfYXRfZGlhZ25vc2lzKQpgYGAKClRoZXJlIGlzIHVzdWFsbHkgbW9yZSB0aGFuIG9uZSB3YXkgb2YgY29tcGxldGluZyBhIHRhc2sgaW4gUi4gSW4gdGhpcyBpbnN0YW5jZSwgd2UgY291bGQgYWxzbyB1c2UgdGhlIGBzdHJfc3ViYCBmdW5jdGlvbiBpbiBgc3RyaW5ncmAgdG8gZXh0cmFjdCBhICJzdWJzdHJpbmciIGZyb20gZWFjaCBlbnRyeSBpbiB0aGUgY29sdW1uLiBUaGUgYXJndW1lbnQgYGVuZD0tM2Agc3BlY2lmaWVzIHRoYXQgdGhlIGV4dHJhY3Rpb24gc2hvdWxkIGVuZCB0aHJlZSBjaGFyYWN0ZXJzIGJlZm9yZSB0aGUgZW5kIG9mIGVhY2ggc3RyaW5nLgoKYGBge3J9Cm1lc3N5ICU+JSAKICBtdXRhdGUoaGVpZ2h0X2F0X2RpYWdub3Npcz1zdHJfc3ViKGhlaWdodF9hdF9kaWFnbm9zaXMsIGVuZD0tMykpICU+JSAKICAgIG11dGF0ZShoZWlnaHRfYXRfZGlhZ25vc2lzID0gYXMubnVtZXJpYyhoZWlnaHRfYXRfZGlhZ25vc2lzKSkgJT4lIAogIGFycmFuZ2UoaGVpZ2h0X2F0X2RpYWdub3NpcykKYGBgCgojIyBGaW5hbCBjb2RlIHRvIGNsZWFuIHRoZSBkYXRhCgpGb3IgcmVmZXJlbmNlLCBoZXJlIGlzIHRoZSBmaW5hbCBjb2RlIGNodW5rIHRoYXQgY2FuIGJlIHVzZWQgdG8gY2xlYW4gdGhlIGRhdGEuCgpgYGB7cn0KY2xlYW5lZCA8LSByZWFkX3RzdigidGNnYV9jbGluaWNhbF9NRVNTWS50c3YiLCBuYSA9IGMoIk5VTEwiLCJOQSIpKSAlPiUgCiAgbXV0YXRlKG1lc3N5LCBnZW5kZXIgPSBmb3JjYXRzOjpmY3RfcmVjb2RlKGdlbmRlciwiTUFMRSI9Im1hbGUiKSwKICAgICAgIGdlbmRlciA9IGZvcmNhdHM6OmZjdF9yZWNvZGUoZ2VuZGVyLCJGRU1BTEUiPSJmZW1hbGUiKSkgJT4lIAogICAgbXV0YXRlKGhlaWdodF9hdF9kaWFnbm9zaXM9c3RyX3N1YihoZWlnaHRfYXRfZGlhZ25vc2lzLCBlbmQ9LTMpKSAlPiUgCiAgICBtdXRhdGUoaGVpZ2h0X2F0X2RpYWdub3NpcyA9IGFzLm51bWVyaWMoaGVpZ2h0X2F0X2RpYWdub3NpcykpCmBgYAoK